🐍 آموزش جامع شی‌گرایی در پایتون (OOP)

از مبانی کلاس و شی تا ارث‌بری و پروژه‌های عملی | آکادمی پایتون غیاثی

📘 شی‌گرایی چیست؟ (مفاهیم پایه)

📌 در برنامه‌نویسی شی‌گرا، هر کدی که می‌نویسیم را مثل یک «شی» در نظر می‌گیریم. به همین دلیل به این سبک برنامه‌نویسی، «شی‌گرایی» می‌گویند.
💡 چرا از شی‌گرایی استفاده می‌کنیم؟
- کدها را خواناتر و قابل فهم‌تر می‌کند.
- باعث جلوگیری از تکرار کد می‌شود.
- نگهداری و توسعهٔ نرم‌افزار را آسان‌تر می‌کند.
- دنیای واقعی را بهتر مدل می‌کند.

🏗️ ساختار اولیه کلاس — دستور class و pass

🐍 کلاس خالی با pass
class python:
    pass
🔍 توضیح: دستور class برای تعریف کلاس استفاده می‌شود. pass به عنوان یک جای خالی عمل می‌کند تا کلاس بدون خطا اجرا شود، ولی خودش کاری انجام نمی‌دهد.
🐍 تعریف کلاس pen و ساختن نمونه
class pen:
    pass

a = pen()
b = pen()
print(a)
print(b)
چرا آدرس‌های متفاوت چاپ می‌شود؟
چون هر نمونه (شی) از کلاس، یک فضای مجزا در حافظه دارد. a و b دو شیء متفاوت هستند، پس آدرس حافظهٔ متفاوتی هم دارند.

🎨 اضافه کردن ویژگی (Attribute) به صورت دستی

🐍 اضافه کردن ویژگی دستی
class pen:
    pass

a = pen()
b = pen()

a.color = 'red'
a.size = 12
a.name = 'a001'

b.color = 'green'
b.size = 12
b.name = 'a001'

print(a.size)  # خروجی: 12
⚠️ مشکل این روش: برای هر نمونه باید تک‌تک ویژگی‌ها را دستی اضافه کنیم. این کار در پروژه‌های بزرگ غیرحرفه‌ای و خسته‌کننده است.

⚙️ روش اتوماتیک با تابع __init__ (سازنده)

🐍 استفاده از __init__
class pen:
    def __init__(self, color, size, name):
        print(color)
        print(size)
        print(name)
        print('--------------')

a = pen('green', 12, 'a001')
b = pen('red', 12, 'a001')
نکته مهم: تابع __init__ به صورت خودکار هنگام ساختن یک شیء جدید اجرا می‌شود. دیگر نیازی به خیره سازی دونه به دونه ویژگی ها نیست و لازم نیست تابع را جداگانه صدا بزنیم.

💾 ذخیره ویژگی‌ها با استفاده از self

🐍 ذخیره ویژگی با self
class pen:
    def __init__(self, color, size, name):
        self.color = color
        self.size = size
        self.name = name

a = pen('green', 12, 'a001')
b = pen('red', 12, 'a001')

print(a.color)   # خروجی: green
📘 توضیح self:
  • self به خود آن شیء اشاره می‌کند.
  • self.color = color یعنی مقدار color که به تابع داده شده را در ویژگی color همان شیء ذخیره کن.
  • به این ترتیب بعداً با a.color می‌توانیم به مقدار ذخیره شده دسترسی پیدا کنیم.

📧 پروژه عملی: ساخت خودکار ایمیل

🐍 ساخت ایمیل اتوماتیک با کلاس
class info:
    def __init__(self, fname, lname):
        self.fname = fname
        self.lname = lname
        self.emil = f'{fname}_{lname}@gmil.com'

a = info('yasin', 'palangi')
b = info('mojtaba', 'sedaghatjo')

print(a.emil)
print(b.emil)
توضیح: این کلاس با دریافت نام و نام خانوادگی، به صورت خودکار یک ایمیل (با فرمت مشخص) می‌سازد و ذخیره می‌کند. بعداً می‌توانیم با استفاده از ویژگی emil آن را چاپ کنیم.

📌 متد (Method) چیست؟

وقتی یک تابع داخل class قرار می‌گیرد، به آن متد می‌گوییم. متدها کارهایی هستند که شیء می‌تواند انجام دهد.

🐍 تعریف متد در کلاس
class info:
    def __init__(self, fname, lname, age):
        self.fname = fname
        self.age = age
        self.lname = lname
        self.emil = f'{fname}_{lname}@gmil.com'

    def bio(self):
        return f'{self.fname} {self.lname} is {self.age} years old'

a = info('yasin', 'palangi', 21)
b = info('mojtaba', 'sedaghatjo', 45)

print(info.bio(a))
print(info.bio(b))
💡 نکته: روش خلاصه‌تر برای صدا زدن متد: print(a.bio()) و print(b.bio()). این روش خواناتر و حرفه‌ای‌تر است.

🧬 مبحث ارث‌بری (Inheritance) — به زبان ساده

📌 ارث‌بری یعنی یک کلاس (فرزند) بتواند تمام ویژگی‌ها و متدهای کلاس دیگر (والد) را بدون نوشتن دوباره، به ارث ببرد و در صورت نیاز آن‌ها را تغییر دهد یا به آن اضافه کند.

فرض کنید شما یک «نقشه» یا «قالب» برای ساختن یک ساختمان دارید. بعداً شخص دیگری می‌آید و می‌گوید: «من هم همین نقشه را می‌خواهم، ولی باید یک پارکینگ هم به آن اضافه کنم.» در برنامه‌نویسی، به جای اینکه کل کد را از صفر بنویسیم، از همان کلاس قبلی ارث‌بری می‌کنیم و فقط قسمت جدید را اضافه می‌کنیم.

💡 مزیت ارث‌بری: جلوگیری از تکرار کد (DRY)، افزایش قابلیت نگهداری و توسعهٔ آسان نرم‌افزار.

🏢 مثال عملی ۱ — کلاس ساختمان و ارث‌بری برای افزودن پارکینگ

ابتدا یک کلاس ساده به نام mybilding می‌سازیم که ویژگی‌های آدرس، تعداد طبقات و مساحت را دارد و یک متد برای نمایش مشخصات.

🏗️ کلاس والد (ساختمان)
class mybilding:
    def __init__(self, adress, floor, area):
        self.adress = adress
        self.floor = floor
        self.area = area
    def biography(self):
        a = f'there is a bilding at {self.adress} the area is : {self.area} .with {self.floor} floors'
        return a

first = mybilding('iran_bushehr', 12, 100)
print(first.biography())
🔍 توضیح: در این کد، یک شیء به نام first با آدرس «ایران_بوشهر»، ۱۲ طبقه و مساحت ۱۰۰ متر ساخته شده و متد biography() یک رشتهٔ توصیفی را برمی‌گرداند.

حالا می‌خواهیم یک کلاس جدید بسازیم که تمام ویژگی‌های کلاس بالا را داشته باشد و یک ویژگی جدید به نام parking (تعداد جای پارک) به آن اضافه کند. اینجا از ارث‌بری استفاده می‌کنیم.

🧬 ارث‌بری با super()
class myfrindbilding(mybilding):
    def __init__(self, adress, floor, area, parking):
        super().__init__(adress, floor, area)
        self.parking = parking

second = myfrindbilding('iran_tehran', 23, 20000, 20)
print('تعداد پارکینگ:', second.parking)
print(second.biography())
نکات کلیدی ارث‌بری در این مثال:
- class myfrindbilding(mybilding): یعنی کلاس جدید از کلاس mybilding ارث می‌برد.
- تابع super() به کلاس والد اشاره دارد. با super().__init__(...) سازندهٔ کلاس والد را صدا می‌زنیم تا ویژگی‌های آدرس، طبقه و مساحت مقداردهی شوند.
- سپس ویژگی جدید self.parking را اضافه می‌کنیم.
- هر متدی که در کلاس والد وجود داشته باشد (مثل biography) به صورت خودکار در کلاس فرزند قابل استفاده است.

🔁 ارث‌بری چندگانه (Multiple Inheritance) — آشنایی مقدماتی

در پایتون، یک کلاس می‌تواند از چند کلاس به طور همزمان ارث‌ببرد. به این کار «ارث‌بری چندگانه» می‌گویند.

🧩 مثال ساده (بدون super در همه)
class a:
    def __init__(self):
        print('a class')

class b:
    def __init__(self):
        print('b class')

class c(a, b):
    def __init__(self):
        super().__init__()
        print('c class')

ins = c()
print(ins)
🔍 توضیح: در اینجا class c(a,b) ابتدا از a و سپس از b ارث می‌برد. اما تابع super() فقط سازندهٔ اولین کلاس (یعنی a) را فراخوانی می‌کند. به همین دلیل پیام 'b class' چاپ نمی‌شود.

برای اینکه همهٔ کلاس‌های والد مقداردهی شوند، باید در هر کلاس نیز از super() استفاده کنیم تا زنجیره‌وار همه فراخوانی شوند.

✅ ارث‌بری چندگانه صحیح با super() زنجیره‌ای
class a:
    def __init__(self):
        super().__init__()
        print('a class')

class b:
    def __init__(self):
        super().__init__()
        print('b class')

class c(b, a):
    def __init__(self):
        super().__init__()
        print('c class')

ins = c()
⚠️ نکته مهم: در ارث‌بری چندگانه، ترتیب کلاس‌ها در تعریف (class c(b,a):) مشخص می‌کند که کدام کلاس زودتر جستجو شود. تابع super() با پیروی از ترتیب حل ارث‌بری (MRO)، تمام کلاس‌های والد را به ترتیب صدا می‌زند.

🧮 پروژه عملی ۱ — ساخت ماشین حساب با استفاده از کلاس

📟 ماشین حساب شی‌گرا
class m:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def add(self):
        return self.a + self.b

    def sub(self):
        return self.a - self.b

    def mul(self):
        return self.a * self.b

    def div(self):
        if self.b == 0:
            print('error: division by zero is not allowed')
            return None
        return self.a / self.b

ins = m(10, 5)
print('جمع:', ins.add())
print('تفریق:', ins.sub())
print('ضرب:', ins.mul())
print('تقسیم:', ins.div())
📘 توضیح گام به گام:
- __init__ دو عدد a و b را از کاربر می‌گیرد و در ویژگی‌های شی ذخیره می‌کند.
- متد add حاصل جمع را برمی‌گرداند.
- متد sub تفریق را انجام می‌دهد.
- متد mul ضرب را محاسبه می‌کند.
- متد div ابتدا بررسی می‌کند مخرج صفر نباشد؛ اگر صفر بود خطا می‌دهد، در غیر این صورت تقسیم را انجام می‌دهد.
- در نهایت یک شیء از کلاس ساخته و خروجی هر چهار عمل را چاپ می‌کنیم.

📞 پروژه عملی ۲ — طراحی دفترچه تلفن دیجیتال (تمرین)

🎯 هدف پروژه: طراحی یک برنامهٔ شی‌گرا که قابلیت‌های زیر را داشته باشد:
- اضافه کردن مخاطب جدید (نام، شماره تلفن، ایمیل اختیاری)
- جستجوی مخاطب بر اساس نام
- حذف مخاطب با استفاده از نام
- مشاهدهٔ لیست تمام مخاطبان
- (اختیاری) ویرایش اطلاعات مخاطب

راهنمایی: یک کلاس به نام ContactManager بسازید. در متد __init__ یک لیست خالی برای ذخیره مخاطبان ایجاد کنید. سپس متدهایی با نام‌های add_contact، search_contact، delete_contact و show_all طراحی کنید. برای نمایش زیباتر می‌توانید از حلقه و چاپ جدولی استفاده کنید.

⚠️ تذکر مهم استاد: این پروژه را خودتان پیاده‌سازی کنید و از هوش مصنوعی کپی نکنید. هدف تمرین و تثبیت مفاهیم ارث‌بری و کلاس است. حتی اگر کد کامل نشود، تلاش شما ارزشمند است.

قالب اولیه برای شروع:

📁 قالب اولیه پروژه دفترچه تلفن
class ContactManager:
    def __init__(self):
        self.contacts = []

    def add_contact(self, name, phone, email=''):
        # کد اضافه کردن مخاطب
        pass

    def search_contact(self, name):
        # کد جستجو
        pass

    def delete_contact(self, name):
        # کد حذف
        pass

    def show_all(self):
        # نمایش همه مخاطبان
        pass

manager = ContactManager()
manager.add_contact('یاسین', '09123456789', 'yasin@example.com')
manager.show_all()

✅ پاسخ تشریحی پروژه دفترچه تلفن دیجیتال

📌 راه‌حل کامل پروژه — پیاده‌سازی یک دفترچه تلفن با استفاده از شی‌گرایی در پایتون

در این راه‌حل، از دو کلاس استفاده کرده‌ایم:

برنامه با یک حلقه while True منوی تعاملی به کاربر نشان می‌دهد و کاربر می‌تواند عملیات مورد نظر خود را انتخاب کند.

📞 کد کامل دفترچه تلفن شی‌گرا
class Contact:
    def __init__(self, name, phone):
        self.name = name
        self.phone = phone


class PhoneBook:
    def __init__(self):
        self.contacts = {}

    def add(self, name, phone):
        self.contacts[name] = Contact(name, phone)
        print("مخاطب اضافه شد")

    def delete(self, name):
        if name in self.contacts:
            del self.contacts[name]
            print("مخاطب حذف شد")
        else:
            print("مخاطب پیدا نشد")

    def search(self, name):
        if name in self.contacts:
            print(f"{name} : {self.contacts[name].phone}")
        else:
            print("مخاطب پیدا نشد")

    def show(self):
        if not self.contacts:
            print("دفترچه تلفن خالی است")
        else:
            for name, contact in self.contacts.items():
                print(f"{name} : {contact.phone}")


phonebook = PhoneBook()

while True:
    print("\n--- دفترچه تلفن ---")
    print("1. افزودن مخاطب")
    print("2. جستجوی مخاطب")
    print("3. نمایش همه مخاطبین")
    print("4. حذف مخاطب")
    print("5. خروج")

    choice = input("انتخاب کنید: ")

    if choice == "1":
        name = input("نام را وارد کنید: ")
        phone = input("شماره تلفن را وارد کنید: ")
        phonebook.add(name, phone)

    elif choice == "2":
        name = input("نام مخاطب را وارد کنید: ")
        phonebook.search(name)

    elif choice == "3":
        phonebook.show()

    elif choice == "4":
        name = input("نام مخاطب برای حذف: ")
        phonebook.delete(name)

    elif choice == "5":
        print("خروج از برنامه")
        break

    else:
        print("گزینه نامعتبر است")
📘 توضیح گام به گام کد:

۱. کلاس Contact:
- این کلاس فقط یک سازنده دارد که نام و شماره تلفن را دریافت کرده و در ویژگی‌های self.name و self.phone ذخیره می‌کند.
- هر مخاطب یک شیء از این کلاس است.

۲. کلاس PhoneBook:
- __init__: یک دیکشنری خالی به نام self.contacts می‌سازد. کلید دیکشنری نام مخاطب و مقدار آن شیء Contact است.
- add(self, name, phone): یک مخاطب جدید با نام و شماره می‌سازد و به دیکشنری اضافه می‌کند.
- delete(self, name): ابتدا بررسی می‌کند آیا مخاطب با این نام وجود دارد یا نه. اگر وجود داشت، آن را حذف می‌کند.
- search(self, name): مخاطب را جستجو می‌کند و اگر پیدا شد، نام و شماره را چاپ می‌کند.
- show(self): اگر دیکشنری خالی باشد پیغام مناسب می‌دهد، در غیر این صورت همه مخاطبان را به همراه شماره چاپ می‌کند.

۳. بخش تعاملی (حلقه while):
- یک منوی متنی به کاربر نشان می‌دهد.
- بر اساس ورودی کاربر، متد مناسب از شیء phonebook صدا زده می‌شود.
- گزینه ۵ باعث خروج از حلقه و پایان برنامه می‌شود.

✅ نکات کلیدی یادگیری از این پروژه:
- استفاده از دیکشنری: دسترسی به مخاطبان با کلید (نام) بسیار سریع و کارآمد است.
- تعامل با کاربر: حلقه while True و شرط‌های if/elif/else یک رابط کاربری ساده و کاربردی ساخته‌اند.
- مدیریت خطا: در متدهای حذف و جستجو، قبل از اقدام، وجود مخاطب بررسی می‌شود.
🎯 تمرین اضافی: سعی کنید قابلیت‌های زیر را به این پروژه اضافه کنید:
- ویرایش اطلاعات مخاطب
- ذخیره ایمیل برای هر مخاطب
- جستجوی شماره تلفن و نمایش نام
- ذخیره دفترچه تلفن در فایل و بازیابی آن در اجرای بعدی

📌 جمع‌بندی نهایی — آنچه در این جزوه کامل یاد گرفتیم

نکته پایانی: شی‌گرایی فقط یک سبک برنامه‌نویسی نیست؛ بلکه روشی برای مدل‌سازی مسائل واقعی جهان پیرامون است. هر چه بیشتر تمرین کنید، راحت‌تر می‌توانید سیستم‌های پیچیده را طراحی و پیاده‌سازی کنید. موفق باشید!
✅ کپی شد!